package org.softee.functional.operation;

import java.util.regex.Pattern;

import org.softee.functional.helper.Preconditions;

public class Predicates {
    private Predicates() {

    }

    public static <T> Predicate<T> isNotNull() {
        return new Predicate<T>() {
            @Override
            public boolean apply(T input) {
                return input != null;
            }
        };
    }

    public static <T> Predicate<T> isEqualTo(final T other) {
        return new Predicate<T>() {
            @Override
            public boolean apply(T input) {
                return (other != null) ? other.equals(input) : (input == null);
            }
        };
    }

    /**
     * @param other (possibly null) the object that data is compared to
     * @return true if input passed to Predicate.apply() is greater than other.
     * Null handling: anything other than null is considered to be greater than null.
     */
    public static <T extends Comparable<T>> Predicate<T> isGreaterThan(final T other) {
        return new Predicate<T>() {
            @Override
            public boolean apply(T input) {
                if (input == null) {
                    return false; // null cannot be larger than anything other
                }
                if (other == null) {
                    return true;
                }
                return input.compareTo(other) > 0;
            }
        };
    }

    /**
     * @param other (possibly null) the object that data is compared to
     * @return true if input passed to Predicate.apply() is less than other.
     * Null handling: anything other than null is considered to be greater than null.
     */
    public static <T extends Comparable<T>> Predicate<T> isLessThan(final T other) {
        return new Predicate<T>() {
            @Override
            public boolean apply(T input) {
                if (other == null) {
                    return false; // no other thing can be smaller than null
                }
                if (input == null) {
                    return true;
                }
                return input.compareTo(other) < 0;
            }
        };
    }

    public static <T> Predicate<T> and(final Predicate<? super T> p1, final Predicate<? super T> p2) {
        Preconditions.notNull(p1);
        Preconditions.notNull(p2);
        return new Predicate<T>() {
            @Override
            public boolean apply(T input) {
                return p1.apply(input) && p2.apply(input);
            }
        };
    }

    public static <T> Predicate<T> or(final Predicate<? super T> p1, final Predicate<? super T> p2) {
        Preconditions.notNull(p1);
        Preconditions.notNull(p2);
        return new Predicate<T>() {
            @Override
            public boolean apply(T input) {
                return p1.apply(input) || p2.apply(input);
            }
        };
    }

    public static <T> Predicate<T> not(final Predicate<T> predicate) {
        Preconditions.notNull(predicate);
        return new Predicate<T>() {
            @Override
            public boolean apply(T input) {
                return !predicate.apply(input);
            }
        };
    }

    public static Predicate<String> isUppercase() {
        return new Predicate<String>() {
            @Override
            public boolean apply(String input) {
                return input != null && input.toUpperCase().equals(input);
            }
        };
    }

    public static Predicate<String> isLowercase() {
        return new Predicate<String>() {
            @Override
            public boolean apply(String input) {
                return input != null && input.toLowerCase().equals(input);
            }
        };
    }

    public static Predicate<CharSequence> matches(String regex) {
        final Pattern pattern = Pattern.compile(regex);
        return new Predicate<CharSequence>() {
            @Override
            public boolean apply(CharSequence input) {
                return input != null && pattern.matcher(input).matches();
            }
        };
    }

    public static <F, T> Predicate<F> compose(final Function<F, T> function, final Predicate<T> predicate) {
        return new Predicate<F>() {
            @Override
            public boolean apply(F input) {
                T value = function.apply(input);
                return predicate.apply(value);
            }
        };
    }
}
